grammar LocStat;

options {
	output=AST;
	//ASTLabelType=LocStatAST;
}

tokens {
DEFINE = '::=';
COUNT = 'N';
MEDIAN = 'MED';
MEAN = 'MEAN';
FRACTION = 'F';
COND_OP = '?:';
SCOPE_OP = '.';
NOT = '!';
ONE_OR_MORE = '+';
EXACTLY_ONE = '?';
SUBJECT;
COND;
COND_DEFAULT;
SCRIPT;
}

@header {
	//package data.analysis.grammar;
}

ID  :	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'0'..'9'|'_')*
    ;

WS  :   ( ' '
        | '\t'
        | '\r'
        | '\n'
        ) {$channel=HIDDEN;}
    ;

STRING
    :  '"' ( ESC_SEQ | ~('\\'|'"') )* '"'
    ;

fragment
HEX_DIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ;

fragment
ESC_SEQ
    :   '\\' ('b'|'t'|'n'|'f'|'r'|'\"'|'\''|'\\')
    |   UNICODE_ESC
    |   OCTAL_ESC
    ;

fragment
OCTAL_ESC
    :   '\\' ('0'..'3') ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7') ('0'..'7')
    |   '\\' ('0'..'7')
    ;

fragment
UNICODE_ESC
    :   '\\' 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
    ;

start
	:	stat+ EOF
	;
	
stat:	STRING DEFINE^ (count|fraction|mean|med) ';'!
	;
	
count
	:	COUNT '(' subj ')' -> ^(COUNT subj)
	;
	
fraction
	:	FRACTION '(' a=subj ',' b=subj ')' -> ^(FRACTION $a $b)
	;
	
mean:	MEAN '(' scopedSubj ')' -> ^(MEAN scopedSubj)
	;
	
med	:	MEDIAN '(' scopedSubj ')' -> ^(MEDIAN scopedSubj)
	;
	
scopedSubj
	:	a=subj SCOPE_OP b=subj -> $a ^(SCOPE_OP $b)
	;
	
subj:	ID (COND_OP condArgs)? -> ^(SUBJECT ID condArgs?)
	|	'(' subj ')' -> subj
	;
	
condArgs
options {backtrack=true;memoize=true;}
	:	cond
	|	'('! cond (','! cond)* ')'!
	;
	
cond
options {backtrack=true;memoize=true;}
	:	numCond NOT -> ^(COND NOT numCond)
	|	numCond
	|	'{' STRING '}' -> ^(COND SCRIPT STRING)
	|	ID NOT -> ^(COND NOT ^(SUBJECT ID))
	|	ID -> ^(COND COND_DEFAULT ^(SUBJECT ID))
	;
	
numCond
options {backtrack=true;memoize=true;}
	:	subj numOp -> ^(COND numOp subj)
	|	'(' numCond ')' -> numCond
	;
	
numOp
	:	ONE_OR_MORE|EXACTLY_ONE
	;